from global_params import *
import re

p_def = r'^define\s+(\w+)\s+(\$*\w+)'
p_imm = r'^#(\$*)(\w+)$'
p_zp_or_ab = r'^\$(\w+)$'
p_zp_or_abs_xy = r'^\$(\w+),(\w)$'
p_idn = r'^\(\$(\w+)\)$'
p_idnx = r'^\(\$(\w+),(\w)\)$'
p_idny = r'^\(\$(\w+)\),(\w)$'
p_label = r'^(\w+):'


ERR_MSG_OP_NUM_LARGE = "[Syntax error] Imm number should not be larger than 0xFF"
ERR_MSG_OP_NUM_FORMAT = "[Syntax error] operation number format error"
      # Name, Imm,  ZP,   ZPX,  ZPY,  ABS,  ABSX, ABSY,  IND, INDX, INDY, SNGL, BRA 

ADDR_IMM = 0
ADDR_ZP = 1
ADDR_ZPX = 2
ADDR_ZPY = 3
ADDR_ABS = 4
ADDR_ABSX = 5
ADDR_ABSY = 6
ADDR_IND = 7
ADDR_INDX = 8
ADDR_INDY = 9
ADDR_SIGL = 10
ADDR_BRA = 11



class Assembler6502:
    def __init__(self):
        pass

    def replace_defs(self,line):
        for k,v in self.defines.items():
            line = line.replace(k,v)
        return line

    def pre_process(self,source):
        self.pc = 0x600
        self.pc_pre = 0x600
        self.labels = {}
        self.defines = {}
        self.codes = []
        self.msg = ""
        self.build_fail = 1
        lines = source.split('\n')
        self.lines = []
        #handle defines
        for l in lines:
            new_line = str(l).strip().split(';')[0]
            m = re.match(p_def,new_line)
            if m:
                self.defines.update({m.groups()[0]:m.groups()[1]})
                new_line = ';'+new_line
            else:
                new_line = self.replace_defs(new_line)
            self.lines.append(new_line)
        #handle labels
        for i in range(len(self.lines)):
            line = self.lines[i]
            m = re.match(p_label,line)
            if m:
                self.labels.update({m.groups()[0]:[i,0]})
        #print self.labels

    def assemble(self,source):
        self.pre_process(source)
        self.pc_line = {}
        for i in range(len(self.lines)):
            self.pc_line.update({self.pc:i})
            self.fail_msg,codes = self.assemble_line(i)
            if self.fail_msg:
                self.msg = self.fail_msg
                break
            else:
                self.msg += '%04d %04x    '%(i+1,self.pc_pre)
                self.msg += "{:<40}".format(self.lines[i])
                self.pc_pre = self.pc
                code_msg = ' '
                for code in codes:
                    if type(code) == type(1):
                        code_msg += "%02x "%code
                    else:
                        code_msg += (code+"_replace")
                        #code_msg += (" " + str(self.labels[code]))
                self.msg += code_msg
                self.msg += '\n'
                self.codes.extend(codes)
        if self.fail_msg:
            self.msg += self.fail_msg
        else: #handle labels
            self.msg += "Assemble passed"
            code_cnt = 0
            self.last_codes = []
            #with open('asb_src.asm','w') as f:
            #    f.write(self.msg)
            for i in range(len(self.codes)):
                if code_cnt % 16 == 0:
                    self.msg += '\n'
                #print "code=",self.codes[i]
                if self.codes[i] in self.labels.keys():
                    if self.codes[i-1] in jmp_op_codes_hex:
                        #print self.labels
                        op_num = self.labels[self.codes[i]][1]
                        #self.msg += "%02x %02x "%(op_num & 0xFF, op_num>>8)
                        self.last_codes.extend([op_num&0xff,op_num>>8])
                        code_cnt += 2
                    else:
                        if code_cnt+0x600 < self.labels[self.codes[i]][1]:
                            #print self.labels,self.codes[i]
                            #self.msg += "%02x "%(self.labels[self.codes[i]][1] - 0x600 - code_cnt - 1)
                            self.last_codes.append(self.labels[self.codes[i]][1] - 0x600 - code_cnt - 1)
                        else:
                            #self.msg += "%02x "%(0xff - code_cnt - 0x600 + self.labels[self.codes[i]][1])
                            self.last_codes.append(0xff - code_cnt - 0x600 + self.labels[self.codes[i]][1])
                        code_cnt += 1
                else:
                    #self.msg += "%02x "%self.codes[i]
                    self.last_codes.append(self.codes[i])
                    code_cnt += 1
            for i in range(len(self.last_codes)):
                if i%32 == 0:
                    self.msg += "\n"
                self.msg += "%02x "%self.last_codes[i]
            self.build_fail = 0
            for k,v in self.labels.items():
                self.msg = re.sub(r'\s'+k+'_replace'," %04x"%v[1],self.msg)
                #self.msg = re.sub("\s"+k+"\s"," %04x"%v[1],self.msg)
            #print self.labels
            #with open('as_src.asm','w') as f:
            #    f.write(self.msg)

    def assemble_line(self,i):
        """
        return fail_msg,codes = []
        """
        #print "Assemble line %d: %s"%(i+1,self.lines[i])
        line = self.lines[i].strip()
        line = line.split(';')[0] #remove comments
        if not line:
            return "",[]
        m = re.match(p_label,line)
        #print "pc=%x"%self.pc
        if m:
            label = m.groups()[0]
            self.labels[label][1] = self.pc 
            #print '---',self.labels
            return "",[]
        try:
            sp = re.split('\s+',line)
            #print sp
            if len(sp) == 2:
                #print 21
                code,addr = sp
                code = code.upper()
                #addr = addr.lower()
            elif len(sp) == 1:
                code = sp[0].upper()
                self.pc += 1
                return "",[opcodes[valid_op_codes.index(code)][-2]]
            else:
                #print 23
                code = sp[0].upper()
                addr = "".join(sp[1:])
            #print 24
            if code not in valid_op_codes:
                return  "Line %d: opcode not valid"%(i+1) + code,[]
            if code in signle_op_codes:
                #print 25
                code = opcodes[valid_op_codes.index(code)][-2]
                self.pc += 1
                return "",[code]
            elif code in brk_op_codes:
                #print 26
                if addr not in self.labels.keys():
                    return "Line %d: branch to an unkown position '%s'"%(i+1,addr), []
                else:
                    code = opcodes[valid_op_codes.index(code)][-1]
                    self.pc += 2
                    return "", [code,addr]
            elif code in single_or_not_single_codes:
                #print 27
                if addr.upper() == 'A':
                    code = opcodes[valid_op_codes.index(code)][-2]
                    self.pc += 1
                    return "",[code]
            #elif code in jmp_op_codes:
            #    print 71,code,addr
            #    if self.labels.has_key(addr):
            #        return "",[opcodes[valid_op_codes.index(code)][5],addr]
            #print 7
            fail_msg,addr,addr_type = self.get_addr(addr)
            #print 8
            if fail_msg:
                #print 9
                return fail_msg,[]
            else:
                #print 10
                codes = [opcodes[valid_op_codes.index(code)][addr_type+1]]
                if codes[0] == 'null':
                    return "Line %d: syntax error"%(i+1),[]
                #print codes
                #print 11
                codes.extend(addr)
                #print 12
                return "",codes
        except:
            return "Line %d: syntax error"%(i+1),[]

    def get_addr(self,addr):
        '''
        addressing:
        None:       0
        #($)xx:     1
        $xx:        1
        $xxxx:      2
        $xx,x|y:    1
        ($xxxx):    2
        ($xx,x|y):  1
        ($xx),x|y:  1
        '''
        #OP #$A0
        #OP #A0
        #print "Assemble addr ",addr
        fail_msg = "" 
        m = re.match(p_imm,addr)
        #print 100
        if m:
            #print "p_imm"
            is_hex,addr = m.groups()
            try:
                if is_hex:
                    addr = int(addr,16)
                else:
                    addr = int(addr)
                if addr > 255:
                    fail_msg = ERR_MSG_OP_NUM_LARGE
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
                addr = 0
            self.pc += 2
            return fail_msg, [addr],ADDR_IMM

        #OP $C000
        #OP $A0
        #print 101
        m = re.match(p_zp_or_ab,addr)
        if m:
            #print "p_zp_or_ab"
            addr_type = {1:ADDR_ZP,2:ADDR_ABS}
            try:
                addr = int(m.groups()[0],16)
                if addr > 0xFFFF:
                    fail_msg = ERR_MSG_OP_NUM_FORMAT
                elif addr > 0xFF:
                    addr = [addr&0xFF,addr>>8]
                    self.pc += 3
                else:
                    addr = [addr]
                    self.pc += 2
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
                addr = [0]
            return fail_msg,addr,addr_type[len(addr)]

        #OP $A0,X|Y
        #OP $C000,X|Y
        m = re.match(p_zp_or_abs_xy,addr)
        #print 102
        if m:
            #print "p_zp_or_abs_xy"
            addr,xy = m.groups()[0:2]
            xy = xy.lower()
            try:
                addr = int(m.groups()[0],16)
                if addr > 0xFFFF:
                    fail_msg = ERR_MSG_OP_NUM_LARGE
                elif addr > 0xFF:
                    addr_type = {'x':ADDR_ABSX,'y':ADDR_ABSY}
                    addr = [addr&0xFF,addr>>8]
                    self.pc += 3
                else:
                    addr_type = {'x':ADDR_ZPX,'y':ADDR_ZPY}
                    addr = [addr]
                    self.pc += 2
                if xy not in ['x','y']:
                    fail_msg = ERR_MSG_OP_NUM_FORMAT
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
                addr = [0]
            return fail_msg,addr,addr_type[xy]

        #OP ($C000)
        #print 103
        m = re.match(p_idn,addr)
        if m:
            #print "p_idn"
            addr = m.groups()[0]
            try:
                addr = int(m.groups()[0],16)
                if addr > 0xFFFF:
                    fail_msg = ERR_MSG_OP_NUM_LARGE
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
            self.pc += 3
            return fail_msg,[addr&0xFF,addr>>8],ADDR_IND

        #OP ($A0,X)
        #print 104
        m = re.match(p_idnx,addr)
        if m:
            #print "p_idnx"
            addr,x = m.groups()[0:2]
            try:
                addr = int(m.groups()[0],16)
                if addr > 0xFF:
                    fail_msg = ERR_MSG_OP_NUM_LARGE
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
            if x.lower() != 'x':
                fail_msg = ERR_MSG_OP_NUM_FORMAT
            self.pc += 2
            return fail_msg,[addr],ADDR_INDX

        #OP ($A0),Y
        #print 105
        m = re.match(p_idny,addr)
        if m:
            #print "p_idny"
            addr,y = m.groups()[0:2]
            try:
                addr = int(m.groups()[0],16)
                if addr > 0xFF:
                    fail_msg = ERR_MSG_OP_NUM_LARGE
            except:
                fail_msg = ERR_MSG_OP_NUM_FORMAT
            if y.lower() != 'y':
                fail_msg = ERR_MSG_OP_NUM_FORMAT
            self.pc += 2
            return fail_msg,[addr],ADDR_INDY

        #print 107
        #JMP label
        #print "label"
        if addr in self.labels.keys():
            self.pc += 3
            return "",[addr],ADDR_ABS
        else:
            return ERR_MSG_OP_NUM_FORMAT,[],ADDR_ABS

def decode():
    opcode_trans = []
    for opcode in opcodes:
        print(opcode[0]),
        for i in range(1,13):
            if opcode[i] != 'null':
                print("{0:08b}".format(opcode[i])),
            else:
                print("        "),
        print ("")

